public class 分布式事务笔记 {
/*
分布式事务

本地事务，也就是传统的**单机事务**。在传统数据库事务中，必须要满足四个原则：
事务的4大特性(ACID)
Atomicity 原子性
Consistency 一致性
Isolation (事务之间的)隔离性
Durability 持久性

在分布式系统下，一个业务跨越多个服务或数据源，每个服务都是一个分支事务，要保证所有分支事务最终状态一致，这样的事务就是分布式事务。
*/

/*
CAP定理

分布式系统有三个指标
- Consistency（一致性）：用户访问分布式系统中的任意节点，得到的数据必须一致。
    当分布式系统中某个节点数据修改后,必须及时同步给其他节点才能保住一致性
- Availability（可用性）：用户访问集群中的任意健康节点，必须能得到响应，而不是超时或拒绝。
- Partition tolerance （分区容错性）
    Partition（分区）：因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接，形成独立分区。
    Tolerance（容错）：在集群出现分区时，整个系统也要持续对外提供服务
这三个指标不可能同时做到,顶多同时实现其二,这个结论就叫做 CAP 定理。

在分布式系统中，系统间的网络不能100%保证健康，一定会有故障的时候，而服务有必须对外保证服务。因此Partition Tolerance不可避免。
当节点接收到新的数据变更时，就会出现问题了：
如果此时要保证**一致性**，就必须等待网络恢复，完成数据同步后，整个集群才对外提供服务，服务处于阻塞状态，不可用。
如果此时要保证**可用性**，就不能等待网络恢复，那node01、node02与node03之间就会出现数据不一致。
也就是说，在P(分区)一定会出现的情况下，A(可用性)和C(一致性)之间只能实现一个。


思考：elasticsearch集群是CP还是AP？
ES集群出现分区时，故障节点会被剔除集群，数据分片会重新分配到其它节点，保证数据一致。因此是低可用性，高一致性，属于CP
*/

/*
BASE理论

BASE理论是对CAP的一种解决思路，包含三个思想：
- **Basically Available** **（基本可用）**：分布式系统在出现故障时，允许损失部分可用性，即保证核心可用。
- **Soft State（软状态）：**在一定时间内，允许出现中间状态，比如临时的不一致状态。
- **Eventually Consistent（最终一致性）**：虽然无法保证强一致性，但是在软状态结束后，最终达到数据一致。


分布式事务最大的问题是各个子事务的一致性问题，因此可以借鉴CAP定理和BASE理论，有两种解决思路：
- AP模式：各子事务分别执行和提交，允许出现结果不一致，然后采用弥补措施恢复数据即可，实现最终一致。
- CP模式：各个子事务执行后互相等待，同时提交，同时回滚，达成强一致。但事务等待过程中，处于弱可用状态。


但不管是哪一种模式，都需要在子系统事务之间互相通讯，协调事务状态，也就是需要一个**事务协调者(TC = Transaction Coordinator)**：
这里的子系统事务，称为**分支事务**；有关联的各个分支事务在一起称为**全局事务**。


#总结
解决分布式事务的思想和模型：
全局事务：整个分布式事务
分支事务：分布式事务中包含的每个子系统的事务
最终一致思想(AP模式)：各分支事务分别执行并提交，如果有不一致的情况，再想办法恢复数据
强一致思想(CP模式)：各分支事务执行完业务不要提交，等待彼此结果。而后统一提交或回滚
*/

/*
Seata架构

Seata 是什么?
Seata 是阿里开源的一款分布式事务解决方案，致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式，为用户打造一站式的分布式解决方案。


Seata事务管理中有三个重要的角色：
TC (Transaction Coordinator) - 事务协调者：维护全局和分支事务的状态，协调全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器：定义全局事务的范围、开始全局事务、提交或回滚全局事务。Coordinator
RM (Resource Manager) - 资源管理器：管理分支事务处理的资源，与TC交谈以注册分支事务和报告分支事务的状态，并驱动分支事务提交或回滚。

速记: TM管理全局事务 RM管理分支事务 TC监控所有事务流程


Seata提供了四种不同的分布式事务解决方案：
XA模式：强一致性分阶段事务模式，牺牲了一定的可用性，无业务侵入
AT模式：最终一致的分阶段事务模式，无业务侵入，也是Seata的默认模式
TCC模式：最终一致的分阶段事务模式，有业务侵入
SAGA模式：长事务模式，有业务侵入
 */

/*
微服务集成Seata

## 1.引入依赖
首先，我们需要在微服务中引入seata依赖：

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!--版本较低，1.3.0，因此排除-->
        <exclusion>
            <artifactId>seata-spring-boot-starter</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>${seata.version}</version>
</dependency>

## 2.修改配置文件
需要修改application.yml文件，添加一些配置：

seata:
  registry: # TC服务注册中心的配置，微服务根据这些信息去注册中心获取tc服务地址
    # 参考tc服务自己的registry.conf中的配置
    type: nacos
    nacos: # tc
      server-addr: 127.0.0.1:8848
      namespace: ""  # namespace为空，就是默认的public
      group: DEFAULT_GROUP
      application: seata-tc-server # tc服务在nacos中的服务名称
      cluster: SH
  tx-service-group: TransactionGroupName # 事务组，根据这个获取tc服务的cluster名称
  service:
    vgroup-mapping: # 事务组与TC服务cluster的映射关系
      TransactionGroupName: SH

#总结
nacos服务名称组成包括？
确定一个注册到Nacos中的微服务需要四个信息：
- namespace：命名空间
- group：分组
- application：服务名
- cluster：集群名

seata客户端获取tc的cluster名称方式？
以tx-group-service的值(TransactionGroupName)为key到vgroupMapping中查找其对应的cluster名称(SH)
 */

/*
SEATA分布式事务解决方案 之 XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理（DTP，Distributed Transaction Processing）标准，XA 规范 描述了全局的TM与局部的RM之间的接口，几乎所有主流的数据库都对 XA 规范 提供了支持 ,实现的原理都是基于两阶段提交(2PC,是将整个事务流程分为两个阶段，P（Prepare phase）是指准备阶段，C（Commit phase）是指提交阶段。)

一阶段：

- 事务协调者通知每个事物参与者执行本地事务
- 本地事务执行完成后报告事务执行状态给事务协调者，此时事务不提交，继续持有数据库锁

二阶段：

- 事务协调者基于一阶段的报告来判断下一步操作
  - 如果一阶段都成功，则通知所有事务参与者，提交事务
  - 如果一阶段任意一个参与者失败，则通知所有事务参与者回滚事务



Seata对原始的XA模式做了简单的封装和改造，以适应自己的事务模型，基本架构如图：


一阶段：
TM
    请求TC开启全局事务
RM
    注册分支事务到TC
    执行分支业务sql,但不提交
    报告执行状态到TC
二阶段：
TM
    请求TC提交或回滚全局事务
TC
    TC检测各分支事务执行状态
    如果都成功，通知所有RM提交事务
    如果有失败，通知所有RM回滚事务
RM
    接收TC指令，提交或回滚事务


优缺点

XA模式的优点是什么？
- 事务的强一致性，满足ACID原则。
- 常用数据库都支持，实现简单，并且没有代码侵入

XA模式的缺点是什么？
- 因为一阶段需要锁定数据库资源，等待二阶段结束才释放，性能较差
- 依赖关系型数据库实现事务

 ----------------------------------------------------------------------

实现XA模式

Seata的starter已经完成了XA模式的自动装配，实现非常简单，步骤如下：

1.修改application.yml文件（每个参与事务的微服务），开启XA模式：

seata:
  data-source-proxy-mode: XA # 开启数据源代理的XA模式

2.给发起全局事务的入口方法添加@GlobalTransactional注解，本例中是OrderServiceImpl中的create方法：

@Override
@GlobalTransactional
public Long create(Order order) {
    // 创建订单
    orderMapper.insert(order);
    // 扣余额 ...略
    // 扣减库存 ...略
    return order.getId();
}

3.重启服务并测试
 */

/*
SEATA分布式事务解决方案 之 AT模式

AT模式同样是分阶段提交的事务模型，不过缺弥补了XA模型中资源锁定周期过长的缺陷。

一阶段：
TM
    请求TC开启全局事务
RM
    注册分支事务到TC
    记录更新前后数据快照undo-log撤销日志
    执行业务sql并提交
    报告执行状态到TC
二阶段：
TM
    请求TC提交或回滚全局事务
TC
    TC检测各分支事务执行状态
    如果都成功，通知所有RM删除undo-log即可
    如果有失败，通知所有RM根据undo-log恢复数据到更新前,再删除undo-log
RM
    接收TC指令，执行对应操作

-------------------------------------------------------

AT模式的脏写问题

在多线程并发访问AT模式的分布式事务时，有可能出现脏写问题
解决思路就是引入了全局锁的概念,实现读写隔离。在释放DB锁之前，先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
全局锁：由TC记录当前正在操作某行数据的事务，该事务持有全局锁，具备执行权。

DB锁重试,默认30次，间隔10毫秒,短于全局锁,会先释放,从而避免多事务同时操作产生的死锁问题

全局锁锁定的范围小于DB锁,它只针对交给seata管理的事务生效.其他非seata事务不受全局锁影响,仍然可以操作数据

通过一阶段数据更新后的快照与现在二阶段回滚前实时的数据进行比对,可以得知从一阶段提交释放DB锁后,到二阶段获取DB锁之间,是否有其他事务修改过数据,由此可以的的值是否有非seata事务修改了数据

-------------------------------------------------------

简述AT模式与XA模式最大的区别是什么？
1.XA模式一阶段不提交事务，锁定资源；AT模式一阶段直接提交，不锁定资源。
2.XA模式依赖数据库机制实现回滚；AT模式利用数据快照实现数据回滚。
3.XA模式强一致；AT模式最终一致


AT模式的优点：
1.一阶段完成sql执行后直接提交事务，释放数据库资源，性能比较好
2.利用全局锁实现读写隔离
3.没有代码侵入，框架自动完成回滚和提交
AT模式的缺点：
1.两阶段之间属于软状态，属于最终一致
2.框架的快照功能会影响性能，但比XA模式要好很多



-------------------------------------------------------

实现AT模式

AT模式中的快照生成、回滚等动作都是由框架自动完成，没有任何代码侵入，因此实现非常简单。
只不过，AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。

1）创建数据快照undo_log表，全局锁表
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;


-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table`  (
  `row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`row_key`) USING BTREE,
  INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;


其中lock_table导入到TC服务关联的数据库，undo_log表导入到微服务关联的数据库：

2）修改application.yml文件，将事务模式修改为AT模式即可：
seata:
  data-source-proxy-mode: AT # 默认就是AT

3)与XA模式一样,要给发起全局事务的入口方法添加@GlobalTransactional注解，本例中是OrderServiceImpl中的create方法：

@Override
@GlobalTransactional
public Long create(Order order) {
    // 创建订单
    orderMapper.insert(order);
    // 扣余额 ...略
    // 扣减库存 ...略
    return order.getId();
}

4）重启服务并测试
 */

/*
SEATA分布式事务解决方案 之 TCC模式原理

TCC模式与AT模式非常相似，每阶段都是独立事务，不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法：
Try：资源的检测和预留；
Confirm：完成资源操作业务；要求 Try 成功 Confirm 一定要能成功。
Cancel：预留资源释放，可以理解为try的反向操作。


一阶段：
TM
    请求TC开启全局事务
RM
    注册分支事务到TC
    检查和预留资源(Try)
    报告执行状态到TC
二阶段：
TM
    请求TC提交或回滚全局事务
TC
    TC检测各分支事务执行状态
    如果资源都够了，通知所有RM 业务执行和提交(Confirm)
    如果资源不够，通知所有RM 释放保留资源(Cancel)
RM
    接收TC指令，执行对应操作


#总结
TCC模式的每个阶段是做什么的？
Try：资源检查和预留
Confirm：业务执行和提交
Cancel：预留资源的释放

TCC的优点是什么？
一阶段完成直接提交事务，释放数据库资源，性能好
相比AT模型，无需生成快照，无需使用全局锁，性能最强
不依赖数据库事务，而是依赖补偿操作，可以用于非事务型数据库

TCC的缺点是什么？
有代码侵入，需要人为编写try、Confirm和Cancel接口，太麻烦
软状态，事务是最终一致
需要考虑Confirm和Cancel的失败情况，做好幂等处理



TCC模式实现中的注意点:

保证confirm、cancel接口的幂等性:重复执行多次与执行一次结果相同(判断有没有执行过,有就不再执行)

允许空回滚:当某分支事务的try阶段阻塞时，可能导致全局事务超时而触发二阶段的cancel操作.然后该分支事务在未执行try操作时就要执行cancel操作，这时cancel操作中不能做回滚释放保留资源(Cancel)，因为并没有保留资源,于是只能空回滚。

拒绝业务悬挂:对于已经空回滚的业务，如果try突然不再阻塞继续执行try，而之后并不会confirm或cancel，因为流程早就已经结束了,导致资源被冻结,这就是业务悬挂。应当阻止执行空回滚后的try操作，避免悬挂

为了实现空回滚、防止业务悬挂，以及幂等性要求。我们必须在数据库记录冻结金额的同时，记录当前事务id和执行状态，为此我们设计了一张表：

CREATE TABLE `account_freeze_tbl` (
  `xid` varchar(128) NOT NULL,
  `user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
  `freeze_money` int(11) unsigned DEFAULT '0' COMMENT '冻结金额',
  `state` int(1) DEFAULT NULL COMMENT '事务状态，0:try，1:confirm，2:cancel',
  PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

其中：
- xid：是全局事务id
- freeze_money：用来记录用户冻结金额
- state：用来记录事务状态

---------------------------------------------------------------------------

具体实现

声明TCC接口
TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明，语法如下：

@LocalTCC
public interface TCCService {
    <
    Try逻辑，@TwoPhaseBusinessAction中的name属性要与当前方法名一致，用于指定Try逻辑对应的方法
    >
    @TwoPhaseBusinessAction(name = "prepare", commitMethod = "confirm", rollbackMethod = "cancel")
    void prepare(@BusinessActionContextParameter(paramName = "param") String param);
    <↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    try方法中可能有些参数在confirm或cancel中需要用到,可以使用@BusinessActionContextParameter注解标注其属性paramName的值需要与形参名一致,在confirm和cancel的形参BusinessActionContext context中就包含有该参数
    >

    <
    二阶段confirm确认方法、可以另命名，但要保证与commitMethod一致
    @param context 上下文,可以传递try方法的参数
    @return boolean 执行是否成功
    >
    boolean confirm (BusinessActionContext context);

    <
    二阶段回滚方法，要保证与rollbackMethod一致
    >
    boolean cancel (BusinessActionContext context);
}

*/

/*
## 四种模式对比

我们从以下几个方面来对比四种实现：
- 一致性：能否保证事务的一致性？强一致还是最终一致？
- 隔离性：事务之间的隔离性如何？
- 代码侵入：是否需要对业务代码改造？
- 性能：有无性能损耗？
- 场景：常见的业务场景

	    XA	        AT	            TCC	                SAGA
一致性	强一致	    弱一致	        弱一致	            最终一致
隔离性	完全隔离	基于全局锁隔离	基于资源预留隔离	无隔离
代码侵入无	        无	            有，要编写三个接口	有，要编写状态机和补偿业务
性能	差	        好	            非常好	            非常好
场景	对一致性、隔离性有高要求的业务
                    基于关系型数据库的大多数分布式事务场景都可以
                                    对性能要求较高的事务。有非关系型数据库要参与的事务。
                                                        业务流程长、业务流程多.参与者包含其它公司或遗留系统服务，无法提供 TCC 模式要求的三个接口
 */

/*
Seata的高可用(集群、异地容灾)

Seata的TC服务作为分布式事务核心，一定要保证集群的高可用性。
搭建TC服务集群非常简单，启动多个TC服务，注册到Nacos注册中心即可。

但集群并不能确保100%安全，万一集群所在机房故障怎么办？所以如果要求较高，一般都会做异地多机房容灾。

微服务基于Nacos配置中心中配置文件里事务组（tx-service-group)与TC集群cluster的映射关系，查找当前应该使用哪个TC集群。当SH集群故障时，只需要将配置项vgroup-mapping中的映射关系改成HZ。则所有微服务就会切换到HZ的TC集群了。

微服务的yml配置文件(存放在配置中心可以不停机热更新)中:
seata:
  tx-service-group: TransactionGroupName # 事务组名，根据这个获取tc服务的cluster名称
  service:
    vgroup-mapping: # 事务组名与TC Server cluster(事务协调者服务器集群)名称的映射关系
      TransactionGroupName: SH  # TC Server cluster(事务协调者服务器集群)名称

----------------------------------------------------------------
具体操作

1.Nacos中新建一个properties配置：

    service.vgroupMapping.TransactionGroupName=SH  # 事务组映射关系

2.需要修改每一个微服务的application.yml文件，让微服务读取nacos中的client.properties文件：

seata:
  data-source-proxy-mode: AT  # 开启数据源代理的XA模 式

  registry: # TC服务注册中心的配置，微服务根据这些信息去注册中心获取tc服务地址
    # 参考tc服务自己的registry.conf中的配置
    type: nacos
    nacos: # tc
      server-addr: 127.0.0.1:8848
      namespace: "" # namespace为空，就是默认的public
      group: DEFAULT_GROUP
      application: Seata-TransactionCoordinator-Server # tc服务在nacos中的服务名称
      username: nacos
      password: nacos

      重点看这里
    ↓↓↓↓↓↓↓

  tx-service-group: TransactionGroupName # 事务组名，根据这个获取tc服务的cluster名称
 #service:
 #  vgroup-mapping: # 事务组名与TC Server cluster(事务协调者服务器集群)名称的映射关系
 #    TransactionGroupName: SH  # TC Server cluster(事务协调者服务器集群)名称
 #    ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
 #原来本地配置的实物组名与集群名映射关系可以注掉也可以留着,作为默认集群来使用

  config: # 配置中心的配置文件，微服务根据这些信息去配置中心获取配置文件当中有事务组名tx-service-group与集群名cluster映射的关系,用于微服务不停机切换SeataTC集群
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      data-id: client.properties # 配置文件名


3.重启微服务，现在微服务到底是连接tc的SH集群，还是tc的HZ集群，都统一由nacos的client.properties来决定了。
 */
}
